package cn.iocoder.yudao.module.resource.dal.redis;

import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.resource.config.EngineLockConfig;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Repository;

import jakarta.annotation.Resource;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

@Slf4j
@Repository
public class ResourceLockRedisDAO {
    private static final Long LOCK_USER_BALANCE_TIMEOUT_MILLIS = 10_000L;
    private static final Long LOCK_USER_ROLE_TIMEOUT_MILLIS = 10_000L;

    private static final Long LOCK_USER_TIME_TIMEOUT_MILLIS = 10_000L;
    private static final Long LOCK_MESSAGE_TIMEOUT_MILLIS = 10_000L;

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private EngineLockConfig engineLockConfig;


    //////////////////////////////////////////////// lockUserBalance

    public <T> T lockUserBalance(Long userId, Callable<T> callable) {
        return lockUserBalance(userId, LOCK_USER_BALANCE_TIMEOUT_MILLIS, callable);
    }

    public <T> T lockUserBalance(Long userId, Long timeoutMillis, Callable<T> callable) {
        final String lockKey = formatUserBalance(userId);
        return lock(timeoutMillis, callable, lockKey);
    }

    private static String formatUserBalance(Long userId) {
        return String.format(ResourceRedisKeyConstants.LOCK_USER_BALANCE, userId);
    }

    //////////////////////////////////////////////// lockUserRole

    public <T> T lockUserRole(Long userId, Long roleId, Integer storyId, Callable<T> callable) {
        return lockUserRole(userId, roleId, storyId, LOCK_USER_ROLE_TIMEOUT_MILLIS, callable);
    }

    public <T> T lockUserRole(Long userId, Long roleId, Integer storyId, Long timeoutMillis, Callable<T> callable) {
        final String lockKey = formatUserRoleIdKey(userId, roleId, storyId);
        return lock(timeoutMillis, callable, lockKey);
    }

    private static String formatUserRoleIdKey(Long userId, Long roleId, Integer storyId) {
        return String.format(ResourceRedisKeyConstants.USER_ROLE_LOCK, userId, roleId, storyId);
    }

    //////////////////////////////////////////////// lockUserTime
    public <T> T lockUserTime(Long userId, Callable<T> callable) {
        if (engineLockConfig.getDisableLock()) {
            try {
                return callable.call();
            } catch (Exception e) {
                if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                } else {
                    throw new RuntimeException(e);
                }
            }
        } else {
            return lockUserTime(userId, LOCK_USER_TIME_TIMEOUT_MILLIS, callable);
        }
    }

    public <T> T lockUserTime(Long userId, Long timeoutMillis, Callable<T> callable) {
        final String lockKey = formatUserTimeKey(userId);
        return lock(timeoutMillis, callable, lockKey);
    }

    private static String formatUserTimeKey(Long userId) {
        return String.format(ResourceRedisKeyConstants.USER_TIME_LOCK, userId);
    }

    //////////////////////////////////////////////// lockMessage

    public <T> T lockMessage(String messageId, Callable<T> callable) {
        if (engineLockConfig.getDisableLock()) {
            try {
                return callable.call();
            } catch (Exception e) {
                if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                } else {
                    throw new RuntimeException(e);
                }
            }
        } else {
            return lockMessage(messageId, LOCK_MESSAGE_TIMEOUT_MILLIS, callable);
        }
    }

    public <T> T lockMessage(String messageId, Long timeoutMillis, Callable<T> callable) {
        final String lockKey = formatMessageKey(messageId);
        return lock(timeoutMillis, callable, lockKey);
    }

    private static String formatMessageKey(String messageId) {
        return String.format(ResourceRedisKeyConstants.MESSAGE_LOCK, messageId);
    }

    private <T> T lock(Long timeoutMillis, Callable<T> callable, String lockKey) {
        final RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
            // 执行逻辑
            return callable.call();
        } catch (Exception e) {
            log.error(StrUtil.format("在分布式锁中执行任务异常, lockKey = {}", lockKey), e);
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new RuntimeException(e);
            }
        } finally {
            lock.unlock();
        }
    }

}
